1 
2 /*
3 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
4 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
5 Authors: Marcelo S. N. Mancini
6 
7 	Copyright Marcelo S. N. Mancini 2018 - 2021.
8 Distributed under the CC BY-4.0 License.
9    (See accompanying file LICENSE.txt or copy at
10 	https://creativecommons.org/licenses/by/4.0/
11 */
12 module hip.game2d.sprite;
13 public import hip.api.renderer.texture;
14 public import hip.api.graphics.color;
15 public import hip.api.data.commons;
16 import hip.math.vector;
17 import hip.api.renderer.shaders.spritebatch;
18 import hip.api.data.textureatlas;
19 import hip.assets.texture;
20 
21 /**
22 *   Encapsulates bunch of sprites to hold a contiguous list of vertices.
23 *   It has some advantages than creating manually an array of similar sprites such as:
24 *   - Copies only once to the SpriteBatch, which searches for the texture index once. 
25 *   - Boundary checks once
26 *   - Makes the sprites array vertices linear, reducing cache misses.
27 *   - Wraps the setTexture and draw process so no need to manually execute the foreach
28 */
29 class HipMultiSprite
30 {
31     protected HipSpriteVertex[] vertices;
32     HipSprite[] sprites;
33     IHipTexture texture;
34     this(size_t spritesCount)
35     {
36         vertices = new HipSpriteVertex[4*spritesCount];
37         sprites = new HipSprite[spritesCount];
38         foreach(i; 0..spritesCount)
39             sprites[i] = new HipSprite(vertices[i*4..(i+1)*4]);
40     }
41 
42     ref HipSprite opIndex(size_t index){return sprites[index];}
43 
44     int opApply(scope int delegate(ref HipSprite) dg)
45     {
46         int result = 0;
47         foreach (item; sprites)
48         {
49             result = dg(item);
50             if (result)
51                 break;
52         }
53         return result;
54     }
55 
56     
57     int opApply(scope int delegate(size_t index, ref HipSprite) dg)
58     {
59         int result = 0;
60         foreach (i, item; sprites)
61         {
62             result = dg(i, item);
63             if (result)
64                 break;
65         }
66         return result;
67     }
68 
69 
70     void setTexture(IHipTexture texture)
71     {
72         this.texture = texture;
73         foreach(sp; sprites)
74             sp.setTexture(texture);
75     }
76 
77     ref HipSpriteVertex[] getVertices()
78     {
79         //Vertices is already a data sink for the sprites, so no need to reassign.
80         foreach(i, sp; sprites)
81             sp.getVertices;
82         return vertices;
83     }
84 
85     void draw()
86     {
87         import hip.api.graphics.g2d.renderer2d;
88         foreach(sp; sprites)
89             sp.isDirty = true;
90         drawSprite(texture, cast(ubyte[])getVertices());
91     }
92 }
93 
94 class HipSprite 
95 {
96     IHipTextureRegion texture;
97     HipColor color = HipColor.white;
98     float x = 0, y = 0;
99     float scrollX = 0, scrollY = 0;
100     float rotation = 0;
101     //Tiling == 1 for consistency, 0 the image would disappear
102     float tilingX = 1, tilingY = 1;
103     float scaleX = 1, scaleY = 1;
104 
105     ///Origin is a point that defines that offsets the rendering origin X and Y based on the sprite size
106     float originX = 0.5, originY = 0.5;
107 
108     float u1 = 0, v1 = 0, u2 = 0, v2 = 0;
109 
110     ///Width of the texture region, (u2-u1) * texture.width
111     uint width;
112     ///Height of the texture region, (v2-v1) * texture.height
113     uint height;
114 
115     private bool flippedX, flippedY;
116 
117     protected bool isDirty = true;
118     protected HipSpriteVertex[] vertices;
119 
120     package this(HipSpriteVertex[] sink)
121     {
122         this.vertices = sink;
123         setColor(HipColor.white);
124     }
125 
126     this()
127     {
128         import hip.api;
129         vertices = new HipSpriteVertex[4];
130         setColor(HipColor.white);
131         setTexture(cast()HipDefaultAssets.getDefaultTexture());
132     }
133     this(IHipAssetLoadTask task)
134     {
135         this();
136         setTexture(task);
137     }
138 
139     this(IHipTexture texture)
140     {
141         vertices = new HipSpriteVertex[4];
142         setTexture(texture);
143     }
144     this(IHipTextureRegion region)
145     {
146         vertices = new HipSpriteVertex[4];
147         this.texture = region;
148         width  = region.getWidth();
149         height = region.getHeight();
150         setRegion(region.getRegion());
151     }
152 
153     void setTexture(IHipTexture texture)
154     {
155         import hip.api;
156         this.texture = new HipTextureRegion(texture);
157         width  = texture.getWidth;
158         height = texture.getHeight;
159         setRegion(this.texture.getRegion());
160     }
161     void setTexture(IHipAssetLoadTask task)
162     {
163         import hip.api;
164         HipAssetManager.addOnCompleteHandler(task, (asset)
165         {
166             this.setTexture(cast(IHipTexture)asset);
167         });
168     }
169 
170     final IHipTexture getTexture() { return texture.getTexture();}
171 
172     final void setRegion(float u1, float v1, float u2, float v2)
173     {
174         setRegion(TextureCoordinatesQuad(u1,v1,u2,v2));
175     }
176     void setRegion(IHipTextureRegion region)
177     {
178         width = region.getWidth();
179         height = region.getHeight();
180         texture = region;
181         setRegion(region.getRegion());
182     }
183     void setRegion(TextureCoordinatesQuad c)
184     {
185         this.u1 = c.u1;
186         this.u2 = c.u2;
187         this.v1 = c.v1;
188         this.v2 = c.v2;
189 
190         texture.setRegion(c.u1, c.v1, c.u2, c.v2);
191         width = texture.getWidth;
192         height = texture.getHeight;
193         const float[] v = texture.getVertices();
194 
195         vertices[0].vTexST = Vector2(v[0], v[1]);
196         vertices[1].vTexST = Vector2(v[2], v[3]);
197         vertices[2].vTexST = Vector2(v[4], v[5]);
198         vertices[3].vTexST = Vector2(v[6], v[7]);
199         if(flippedX)
200         {
201             flippedX = false;
202             setFlippedX(true);
203         }
204         if(flippedY)
205         {
206             flippedY = false;
207             setFlippedY(true);
208         }
209     }
210 
211     void setPosition(float x, float y)
212     {
213         if(this.x != x || this.y != y)
214             isDirty = true;
215         this.x = x;
216         this.y = y;
217     }
218     void setOrigin(float x, float y)
219     {
220         if(originX != x || originY != y)
221             isDirty = true;
222         originX = x;
223         originY = y;
224     }
225 
226     ref HipSpriteVertex[] getVertices()
227     {
228         if(isDirty)
229         {
230             isDirty = false;
231             float _x = -cast(float)width*originX * scaleX + x;
232             float _y = -cast(float)height*originY * scaleY + y;
233             float x2 = _x+(width * scaleX);
234             float y2 = _y+(height * scaleY); 
235 
236             if(rotation == 0)
237             {
238                 //Top left
239                 vertices[0].vPosition = Vector3(_x, _y,0);
240 
241                 //Top right
242                 vertices[1].vPosition = Vector3(x2, _y,0);
243 
244                 //Bot right
245                 vertices[2].vPosition = Vector3(x2, y2,0);
246 
247                 //Bot left
248                 vertices[3].vPosition = Vector3(_x, y2,0);
249             }
250             else
251             {
252                 import core.math:sin,cos;
253                 float c = cos(rotation);
254                 float s = sin(rotation);
255 
256                 //Top left
257                 vertices[0].vPosition = Vector3(c*_x - s*_y + this.x, c*_y + s*_x + this.y,0);
258 
259                 //Top right
260                 vertices[1].vPosition = Vector3(c*x2 - s*_y + this.x, c*_y + s*x2 + this.y,0);
261 
262                 //Bot right
263                 vertices[2].vPosition = Vector3(c*x2 - s*y2 + this.x, c*y2 + s*x2 + this.y,0);
264 
265                 //Bot left
266                 vertices[3].vPosition = Vector3(c*_x - s*y2 + this.x, c*y2 + s*_x + this.y,0);
267             }
268         }
269         return vertices;
270     }
271 
272     void setColor(HipColor color)
273     {
274         this.color = color;
275         vertices[0].vColor = color;
276         vertices[1].vColor = color;
277         vertices[2].vColor = color;
278         vertices[3].vColor = color;
279     }
280 
281     void setScale(float scaleX, float scaleY)
282     {
283         this.scaleX = scaleX;
284         this.scaleY = scaleY;
285         isDirty = true;
286     }
287     void setRotation(float rotation)
288     {
289         import hip.math.utils;
290         this.rotation = rotation % (PI * 2);
291         isDirty = true;
292     }
293     ///Same thing as setRotation, but receives in Degrees
294     void setAngle(float angle)
295     {
296         import hip.math.utils:degToRad;
297         setRotation(degToRad(angle));
298     }
299 
300     int getWidth() const {return width;}
301     int getHeight() const {return height;}
302     int getTextureWidth() const {return texture.getTextureWidth();}
303     int getTextureHeight() const {return texture.getTextureHeight();}
304 
305     /**
306     * This function is most useful for single images. For instance backgrounds, probably, if you have a
307     * texture atlas or a spritesheet, this function is not useful
308     */
309     void setScroll(float x, float y)
310     {
311         setRegion(
312             -scrollX + x + u1,
313             -scrollY + y + v1,
314             -scrollX + x + u2,
315             -scrollY + y + v2
316         );
317         scrollX = x;
318         scrollY = y;
319     }
320 
321     void setFlippedX(bool flip)
322     {
323         if(flip != flippedX)
324         {
325             auto reg = texture.getRegion;
326             flippedX = flip;
327             vertices[0].vTexST.x = flip ? reg.u2 : reg.u1;
328             vertices[1].vTexST.x = flip ? reg.u1 : reg.u2;
329             vertices[2].vTexST.x = flip ? reg.u1 : reg.u2;
330             vertices[3].vTexST.x = flip ? reg.u2 : reg.u1;
331         }
332     }
333     void setFlippedY(bool flip)
334     {
335         if(flip != flippedY)
336         {
337             auto reg = texture.getRegion;
338             flippedY = flip;
339             vertices[0].vTexST.y = flip ? reg.v2 : reg.v1;
340             vertices[1].vTexST.y = flip ? reg.v2 : reg.v1;
341             vertices[2].vTexST.y = flip ? reg.v1 : reg.v2;
342             vertices[3].vTexST.y = flip ? reg.v1 : reg.v2;
343         }
344     }
345     bool isFlippedX() => flippedX;
346     bool isFlippedY() => flippedY;
347 
348 
349     /**
350     *   Sets the tiling factor for this sprite. Default is 1.
351     */
352     void setTiling(float x = 1, float y = 1)
353     {
354         assert(x != 0 && y != 0, "Tiling factor equals 0 will disappear the sprite image");
355 
356         setRegion(
357             u1 / tilingX * x,
358             v1 / tilingY * y,
359             u2 / tilingX * x,
360             v2 / tilingY * y
361         );
362         tilingX = x;
363         tilingY = y;
364     }
365 
366     void draw()
367     {
368         import hip.api.graphics.g2d.renderer2d;
369         this.isDirty = true;
370         drawSprite(texture.getTexture, cast(ubyte[])getVertices[]);
371     }
372 }
373 
374 
375 class HipSpriteAnimation : HipSprite
376 {
377     import hip.api.graphics.g2d.animation;
378     private IHipAnimation animation;
379     HipAnimationFrame* currentFrame;
380 
381     this(){super();}
382 
383     this(IHipAnimation anim)
384     {
385         super();
386         animation = anim;
387         this.setAnimation(anim.getCurrentTrackName());
388     }
389 
390     IHipAnimationTrack getAnimation(string animName)
391     {
392         return animation.getTrack(animName);
393     }
394     /**
395     *   Sets internal animation data.
396     */
397     void setAnimation(IHipAnimation anim)
398     {
399         animation = anim;
400         setAnimation(animation.getCurrentTrackName());
401     }
402     void setAnimation(string animName)
403     {
404         animation.play(animName);
405         setFrame(animation.getCurrentFrame());
406     }
407 
408     void setBounds(int width, int height)
409     {
410         this.width = width;
411         this.height = height;
412     }
413 
414     void setFrame(HipAnimationFrame* frame)
415     {
416         this.currentFrame = frame;
417         this.texture = frame.region;
418         setBounds(frame.region.getWidth(), frame.region.getHeight());
419         setRegion(texture.getRegion());
420     }
421 
422     void update(float dt)
423     {
424         animation.update(dt);
425         setFrame(animation.getCurrentFrame());
426     }
427 }